﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Xml.Serialization;
using Argos.Design;
using System.Drawing.Design;
using System.Xml.Schema;
using System.Xml;
using System.IO;
using System.ComponentModel.Design;

namespace Argos.Data.Model
{
	/// <summary>
	/// <para>
	/// Represents a message delivery route. A route consists of a message delivery activity
	/// and a set of properties that are applied to that delivery activity.
	/// </para>
	/// <para>
	/// This enables for instance to specify two different routes for e-mail message delivery
	/// using the same delivery activity but different relay server.
	/// </para>
	/// </summary>
	[Serializable]
	[DefaultProperty("Name")]
	[XmlRoot("messageDeliveryRoute", Namespace = ItemBase.XmlNamespace)]
	public sealed class MessageDeliveryRoute : ItemBase , IXmlSerializable , IEquatable<MessageDeliveryRoute>
	{

		#region public Dictionary<string, object> DeliveryActivityProperties { get; set; }

		private static string DeliveryActivityPropertiesPropertyName = "DeliveryActivityProperties";

		/// <summary>
		/// Sets or returns a dictionary containing the properties that will be applied to
		/// a new delivery activity instance created from the type specified in
		/// <see cref="DeliveryActivityType"/>.
		/// </summary>
		public Dictionary<string, object> DeliveryActivityProperties
		{
			get
			{
				Dictionary<string, object> props = this.GetProperty<Dictionary<string, object>>(DeliveryActivityPropertiesPropertyName);
				if (null == props)
				{
					props = new Dictionary<string, object>();
					this.SetProperty(DeliveryActivityPropertiesPropertyName, props);
				}
				return props;
			}
			set { this.SetProperty(DeliveryActivityPropertiesPropertyName, value); }
		}

		#endregion

		#region public Type DeliveryActivityType { get; set; }

		private static string DeliveryActivityTypePropertyName = "DeliveryActivityType";

		/// <summary>
		/// Sets or returns the type of the message delivery activity used in the current delivery route.
		/// </summary>
		/// <exception cref="ArgumentNullException">The exception that is thrown if this property is set to null.</exception>
		/// <exception cref="ArgumentException">The exception that is thrown if this property is set to a type that does not represent a message delivery activity.</exception>
		[Editor(typeof(MessageDeliveryActivityTypeEditor), typeof(UITypeEditor))]
		public Type DeliveryActivityType
		{
			get { return this.GetProperty<Type>(DeliveryActivityTypePropertyName); }
			set
			{
				if (null == value)
					throw new ArgumentNullException("DeliveryActivityType");

				if (!value.IsMessageDeliveryActivity())
					throw new ArgumentException("The specified type is not a message delivery activity type.", "DeliveryActivityType");

				if (!object.Equals(this.DeliveryActivityType, value))
				{
					this.DeliveryActivityProperties.Clear();
					this.SetProperty(DeliveryActivityTypePropertyName, value);
				}
			}
		}

		#endregion

		#region public string Description { get; set; }

		private static string DescriptionPropertyName = "Description";

		/// <summary>
		/// Sets or returns the description for the delivery route. This property is optional.
		/// </summary>
		[Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))]
		public string Description
		{
			get { return this.GetProperty<string>(DescriptionPropertyName); }
			set { this.SetProperty(DescriptionPropertyName, value); }
		}

		#endregion
		
		#region public string Name { get; set; }

		private static string NamePropertyName = "Name";

		/// <summary>
		/// Sets or returns the name of the route.
		/// </summary>
		public string Name
		{
			get { return this.GetProperty<string>(NamePropertyName); }
			set
			{
				if (string.IsNullOrEmpty(value))
					throw new ArgumentNullException("Name");

				this.SetProperty(NamePropertyName, value);
			}
		}

		#endregion




		public static MessageDeliveryRoute Read(Stream stream)
		{
			return ItemBase.Read<MessageDeliveryRoute>(stream);
		}

		public static MessageDeliveryRoute Read(FileInfo sourceFile)
		{
			return ItemBase.Read<MessageDeliveryRoute>(sourceFile);
		}

		public static MessageDeliveryRoute ReadXml(Stream stream)
		{
			return ItemBase.ReadXml<MessageDeliveryRoute>(stream);
		}

		public static MessageDeliveryRoute ReadXml(FileInfo sourceFile)
		{
			return ItemBase.ReadXml<MessageDeliveryRoute>(sourceFile);
		}



		public override bool Equals(object obj)
		{
			if (obj is MessageDeliveryRoute)
				return this.Equals((MessageDeliveryRoute)obj);

			return false;
		}

		public bool Equals(MessageDeliveryRoute other)
		{
			if (null == other)
				return false;

			if (!Guid.Equals(this.Id, other.Id))
				return false;

			if (!string.Equals(this.Name, other.Name))
				return false;

			if (!string.Equals(this.Description, other.Description))
				return false;

			if (!Type.Equals(this.DeliveryActivityType, other.DeliveryActivityType))
				return false;

			return this.DeliveryActivityProperties.EqualsDictionary(other.DeliveryActivityProperties);
		}

		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		public XmlSchema GetSchema()
		{
			return null;
		}

		public void ReadXml(XmlReader reader)
		{
			if (ItemBase.XmlNamespace != reader.NamespaceURI)
				throw new InvalidOperationException(string.Format("Unrecognized namespace: '{0}'.", reader.NamespaceURI));

			if (reader.LocalName != "messageDeliveryRoute")
				throw new InvalidOperationException(string.Format("Unrecognized element name: '{0}'.", reader.LocalName));

			Dictionary<string, object> properties = new Dictionary<string, object>();

			reader.ReadStartElement();

			while (reader.NodeType == XmlNodeType.Element)
			{
				switch (reader.LocalName)
				{
					case "id":
						this.Id = new Guid(reader.ReadElementContentAsString());
						break;

					case "name":
						this.Name = reader.ReadElementContentAsString();
						break;

					case "deliveryActivityProperties":
						reader.ReadElementContent(properties, "property", "name", "value");
						break;

					case "deliveryActivityType":
						Type t = reader.ReadElementContentAsType();
						if (null != t)
							this.DeliveryActivityType = t;
						break;

					case "description":
						this.Description = reader.ReadElementContentAsString();
						break;
					default:
						throw new InvalidOperationException(string.Format("Unrecognized element: '{0}'.", reader.LocalName));
				}
			}

			reader.ReadEndElement();

			this.DeliveryActivityProperties = properties;

		}

		public void WriteXml(XmlWriter writer)
		{
			if (null == writer)
				throw new ArgumentNullException("writer");

			writer.WriteTypeElement(this.DeliveryActivityType, "deliveryActivityType");

			writer.WriteElementString("id", this.Id.ToString());

			if (!string.IsNullOrEmpty(this.Description))
				writer.WriteElementString("description", this.Description);

			writer.WriteElementString("name", this.Name);
			
			writer.WriteDictionaryElement(this.DeliveryActivityProperties, "deliveryActivityProperties", "property", "name", "value");
		}

	}
}
